package de.hska.util;

import java.lang.reflect.Field;
import java.util.Collection;

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

import org.apache.commons.logging.Log;

/**
 * Interceptor zum Tracing von public-Methoden der Session Beans und MessageDriven Beans (MDB).
 * Sowohl der Methodenaufruf als auch das Methodenende (Rueckgabewert oder Exception) werden mit
 * Level DEBUG protokolliert.
 */
public class StacktraceInterceptor {
	private static final String BEGIN = MessagesUtil.getMessage("log.begin") + ' ';
	private static final String END = MessagesUtil.getMessage("log.end") + ' ';
	private static final String COUNT = MessagesUtil.getMessage("log.count") + ' ';
	private static final int MAX_ELEM = 4;  // bei Collections wird ab 5 Elementen nur die Anzahl ausgegeben

	@AroundInvoke
	public Object log(InvocationContext ctx) throws Exception {
		
		final Object bean = ctx.getTarget();
		final Class<?> clazz = bean.getClass();
		
		// Logger ermitteln: statisches Attribut "LOG" muss public sein
		//                   alternativ: Method m = clazz.getMethod("getLOG");
		//                               Log log = (Log) m.invoke(null);
		Field logAttribut = null;
		try {
			logAttribut = clazz.getField("LOG");
		}
		catch (NoSuchFieldException e) {
			System.err.println("No public LOG field in class " + clazz.getName());
			return ctx.proceed();
		}
		final Log log = (Log) logAttribut.get(null);

		if (!log.isDebugEnabled()) {
			return ctx.proceed();
		}

		final String methodName = ctx.getMethod().getName();
		final Object[] params = ctx.getParameters();

		// Methodenaufruf protokollieren
		final StringBuilder sb = new StringBuilder(BEGIN + methodName);
		if (params != null) {
			final int anzahlParams = params.length;
			sb.append(": ");
			for (int i = 0; i < anzahlParams; i++) {
				// Fuer Arrays gibt es kein toString
				// byte[] ueberspringen wg. potenzieller Binaerdaten
				if (params[i] == null) {
					sb.append("null");
				}
				else {
					final Class<?> paramClass = params[i].getClass();
					if (paramClass == null) {
						// primitiver Wert, z.B. int
						sb.append(params[i]);
					}
					else {
						final String paramStr = toString(params[i]);
						sb.append(paramStr);
					}
				}
				sb.append(", ");
			}
			final int laenge = sb.length();
			sb.delete(laenge-2, laenge-1);
		}
		log.debug(sb.toString());
		
		Object result = null;
//		try {
			// Eigentlicher Methodenaufruf
			result = ctx.proceed();
			
		// Keine Protokollierung der geworfenen Exception:
		// 1) Stacktrace wuerde abgeschnitten werden
		// 2) Exception wird an der Ursprungsstelle bereits protokolliert.
		//    Wenn der StacktraceInterceptor in ejb-jar.xml abgeklemmt wird,
		//    muss naemlich immer noch eine Protokollierung stattfinden.

//		}
//		catch (Exception e) {
//			// Methode hat eine Exception geworfen
//			log.error(methodName + ": " + e.getMessage());
//			throw e;
//		}

		if (result == null) {
			// Methode vom Typ void oder Rueckgabewert null
			log.debug(END + methodName);
		}
		else {
			final String resultStr = toString(result);
			log.debug(END + methodName + ": " + resultStr);
		}
		
		return result;
	}
	
	/**
	 */
	private String toString(Object obj) {
		if (obj instanceof Collection) {
			// Collection: Elemente bei kleiner Anzahl ausgeben; sonst nur die Anzahl
			final Collection<?> coll = (Collection<?>) obj;
			final int anzahl = coll.size();
			if (anzahl > MAX_ELEM) {
				return COUNT + coll.size();
			}
			else {
				return coll.toString();
			}
		}
		else if (obj.getClass().isArray()) {
			// Array: Elemente bei kleiner Anzahl ausgeben; sonst nur die Anzahl
			final Class<?> componentClass = obj.getClass().getComponentType();
			if (!componentClass.isPrimitive()) {
				// Array von Objekten
				final Object[] arr = (Object[]) obj;
				if (arr.length > MAX_ELEM) {
					return COUNT + arr.length;
				}
				else {
					final StringBuilder sbEnd = new StringBuilder("[");
					final int anzahl = arr.length;
					for (int i = 0; i < anzahl; i++) {
						sbEnd.append(arr[i]);
						sbEnd.append(", ");
					}
					final int laenge = sbEnd.length();
					sbEnd.delete(laenge-2, laenge-1);
					sbEnd.append("]");
					return sbEnd.toString();
				}
			}
			else {
				// Array von primitiven Werten: byte, short, int, long, ..., float, double, boolean, char
				if ("short".equals(componentClass.getName()) ||
					"int".equals(componentClass.getName()) ||
					"long".equals(componentClass.getName())) {
					final long[] arr = (long[]) obj;
					if (arr.length > MAX_ELEM) {
						return COUNT + arr.length;
					}
					else {
		
						final StringBuilder sbEnd = new StringBuilder("[");
						final int anzahl = arr.length;
						for (int i = 0; i < anzahl; i++) {
							sbEnd.append(arr[i]);
							sbEnd.append(", ");
						}
						final int laenge = sbEnd.length();
						sbEnd.delete(laenge-2, laenge-1);
						sbEnd.append("]");
						return sbEnd.toString();
					}
				}
				else if ("byte".equals(componentClass.getName())) {
					return "<byte-array>";
				}
				else if ("float".equals(componentClass.getName()) ||
					     "double".equals(componentClass.getName())) {
					final double[] arr = (double[]) obj;
					if (arr.length > MAX_ELEM) {
						return COUNT + arr.length;
					}
					else {
						final StringBuilder sbEnd = new StringBuilder("[");
						final int anzahl = arr.length;
						for (int i = 0; i < anzahl; i++) {
							sbEnd.append(arr[i]);
							sbEnd.append(", ");
						}
						final int laenge = sbEnd.length();
						sbEnd.delete(laenge-2, laenge-1);
						sbEnd.append("]");
						return sbEnd.toString();
					}
				}
				else if ("char".equals(componentClass.getName())) {
					final char[] arr = (char[]) obj;
					if (arr.length > MAX_ELEM) {
						return COUNT + arr.length;
					}
					else {
						final StringBuilder sbEnd = new StringBuilder("[");
						final int anzahl = arr.length;
						for (int i = 0; i < anzahl; i++) {
							sbEnd.append(arr[i]);
							sbEnd.append(", ");
						}
						final int laenge = sbEnd.length();
						sbEnd.delete(laenge-2, laenge-1);
						sbEnd.append("]");
						return sbEnd.toString();
					}
				}
				else if ("boolean".equals(componentClass.getName())) {
					final char[] arr = (char[]) obj;
					if (arr.length > MAX_ELEM) {
						return COUNT + arr.length;
					}
					else {
						final StringBuilder sbEnd = new StringBuilder("[");
						final int anzahl = arr.length;
						for (int i = 0; i < anzahl; i++) {
							sbEnd.append(arr[i]);
							sbEnd.append(", ");
						}
						final int laenge = sbEnd.length();
						sbEnd.delete(laenge-2, laenge-1);
						sbEnd.append("]");
						return sbEnd.toString();
					}
				}
				else
					throw new IllegalStateException();
			}

		}
		else {
			// Objekt, aber keine Collection und kein Array
			return obj.toString();
		}
	}
}
